/**
 *     This file is part of cesm-ws.
 *
 *     cesm-ws, Web Service for Community Earth System Model
 *
 *     Copyright (c) 2010, Purdue University and/or affilicates or thrid-party contributors as
 *     indicated by the @author tags or express copyright attribution statements
 *     applied by the authors. All third-party contributions are distributed under
 *     license by Purdue University.
 *
 *     cesm-ws is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 *
 *     This program is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU General Public License for more details.
 *
 *     You should have received a copy of the GNU Lesser General Public License
 *     along with this distribution.  If not, see <http://www.gnu.org/licenses/>.
 */

package edu.purdue.rcac.service.dispatch;

/**
 * @author Han Zhang
 */
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import javax.activation.DataHandler;
import javax.naming.InitialContext;
import javax.naming.NamingException;

import org.apache.log4j.Logger;
import org.globus.ftp.exception.FTPException;
import org.globus.myproxy.GetParams;
import org.jboss.aop.pointcut.ast.ParseException;
import org.jboss.ejb3.annotation.Depends;
import org.jboss.ejb3.annotation.Service;

import bsh.EvalError;
import bsh.Interpreter;
import edu.purdue.rcac.ccsm.jws.TokenManagerSessionBeanLocal;
import edu.purdue.rcac.grid.submit.SubmissionException;
import edu.purdue.rcac.grid.submit.Submitter;
import edu.purdue.rcac.grid.submit.SubmitterResponse;
import edu.purdue.rcac.grid.util.ByteArrayDataSource;
import edu.purdue.rcac.service.dispatch.AbstractResponse.Outcome;

@Service(objectName = ServiceDispatcherManagement.OBJECT_NAME)
public class ServiceDispatcher implements ServiceDispatcherManagement {

	protected static final Logger logger = Logger
			.getLogger(ServiceDispatcher.class.getName());

	protected static TokenManagerSessionBeanLocal tokenManager;

	protected HashMap<String, AppProfile> appProfileList = new HashMap<String, AppProfile>();

	public void create() throws Exception {
		try {
			// tokenManager initialization: JNDI session bean look up
			logger.info("Look up JNDI name: " + "TokenManagerSessionBean/local");
			InitialContext ctx;
			ctx = new InitialContext();
			tokenManager = (TokenManagerSessionBeanLocal) ctx
					.lookup("TokenManagerSessionBean/local");
		} catch (NamingException ne) {
			logger.error(ne.getMessage(), ne);
			tokenManager = null;
		}
		logger.debug("create()");
	}

	public void destroy() {
		logger.debug("destroy()");
	}

	public void start() throws Exception {
		logger.info("Service started");
	}

	public void stop() {
		logger.info("Service stoped");
	}

	private AppProfile getAppProfile(String appName) {
		if (this.appProfileList.keySet().contains(appName))
			return this.appProfileList.get(appName);
		else
			throw new IllegalArgumentException(
					"no profile associated with app name '" + appName + "'");
	}

	public static String PREFIX = "dispatch:";

	public static String unwrapDispatchParam(String str) throws ParseException {
		if (str.startsWith(PREFIX))
			return str.substring(PREFIX.length() - 1);
		else
			throw new ParseException("invalid dispatch param '" + str + "'");
	}

	public static String wrapDispatchParam(String str) {
		if (str.startsWith(PREFIX))
			return str;
		else
			return PREFIX + str;
	}

	// //////////////////////////////////////////////////////////
	// ////////////////// TOKEN OPERATION //////////////////////
	// //////////////////////////////////////////////////////////
	public String generateToken(String userName, String password,
			AuthDatabase db) {
		switch (db) {
		case PortalDB:
			return tokenManager.RequestTokenPortalDB(userName, password);
		case MyProxy:
			return tokenManager.RequestTokenMyProxy(userName, password);
		default:
			return null;
		}
	}

	public void renewToken(String userName, String token) {
		tokenManager.renewToken(userName, token);
	}

	public boolean verifyToken(String userName, String token) {
		return tokenManager.VerifyTokenBoolean(userName, token);
	}

	public boolean registerApp(String appName,
			SubmitterImpLanguage submitterImpLang, SubmitType submitType) {
		try {
			this.appProfileList.put(appName, new AppProfile(appName,
					submitterImpLang, submitType));
			return true;
		} catch (Exception e) {
			return false;
		}
	}

	// //////////////////////////////////////////////////////////
	// //////////////// SUBMITTER OPERATION ////////////////////
	// //////////////////////////////////////////////////////////
	/**
	 * Load bean shell implementation instance of {@link Submitter} interface <br>
	 * Target script and package should be named by appName, submitType <br>
	 * e.g. appName="cesm", submitType=SubmitType.GLOBUS <br>
	 * target script: "cesm_globus_submitter.bsh" <br>
	 * target package: "edu.purdue.rcac.cesm.submitter.globus" <br>
	 * 
	 * FIXME: target package is not the same as expected, currently the script
	 * is directly put in $JBOSS_HOME/bin/bean-shell-scripts, not as expected
	 * 
	 * @throws EvalError
	 * @throws FileNotFoundException
	 * @throws IOException
	 */
	private Submitter loadBeanShellSubmitter(String appName,
			SubmitType submitType, Map<String, String> argsMap)
			throws EvalError, FileNotFoundException, IOException {
		String scriptName = String.format(
				"bean-shell-scripts/%s_%s_submitter.bsh", appName, submitType
						.toString().toLowerCase());
		Interpreter bshI = new Interpreter();

		logger.info("Loading bean shell script: " + scriptName);
		bshI.set("argsMap", argsMap);
		bshI.source(scriptName);

		return (Submitter) bshI.getInterface(Submitter.class);
	}

	/**
	 * Load java implementation instance of {@link Submitter} interface. <br>
	 * 
	 * Target class and package should be named by appName, submitType <br>
	 * e.g. appName="cesm", submitType=SubmitType.GLOBUS <br>
	 * target class name: "CESMGlobusSubmitter" <br>
	 * target package: "edu.purdue.rcac.cesm.submitter.globus" <br>
	 * 
	 * @throws ClassNotFoundException
	 * @throws InstantiationException
	 * @throws IllegalAccessException
	 */
	private Submitter loadJavaSubmitter(String appName, SubmitType submitType,
			Map<String, String> argsMap) throws ClassNotFoundException,
			InstantiationException, IllegalAccessException {
		String packageName = String.format("edu.purdue.rcac.%s.submitter",
				appName);
		String className = ServiceDispatcher.getCamelWord(String.format(
				"%s_%s_submitter", appName.toUpperCase(), submitType.name()
						.toLowerCase()));
		logger.info("Loading Java class: " + packageName + "." + className);
		Class T = Class.forName(packageName + "." + className);
		Submitter submitter = (Submitter) T.newInstance();
		submitter.setArgsMap(argsMap);
		return submitter;
	}

	/**
	 * submit a job with given argument map
	 * 
	 * @throws InstantiationException
	 * @throws SubmissionException
	 */
	public ServiceResponse submit(String appName, Map<String, String> argsMap)
			throws InstantiationException, SubmissionException {
		// TODO: job management

		// argsMap check
		if (argsMap == null)
			throw new NullPointerException("argsMap is empty");

		logger.debug("------ Argument Map ------");
		for (String key : argsMap.keySet()) {
			logger.debug(String.format("%s => %s", key, argsMap.get(key)));
		}
		logger.debug("--------------------------");

		AppProfile profile = getAppProfile(appName);
		Submitter submitter = loadSubmitter(appName,
				profile.getSubmitterImpLang(), profile.getSubmitType(), argsMap);
		submitter.submit();

		ServiceResponse response = new ServiceResponse(submitter.getResult());
		return response;
	}

	/**
	 * load submitter instance with Submitter interface
	 * 
	 * @throws InstantiationException
	 */
	private Submitter loadSubmitter(String appName,
			SubmitterImpLanguage submitterLanguage, SubmitType submitType,
			Map<String, String> argsMap) throws InstantiationException {
		try {
			switch (submitterLanguage) {
			case BEAN_SHELL:
				return loadBeanShellSubmitter(appName, submitType, argsMap);
			case JAVA:
				return loadJavaSubmitter(appName, submitType, argsMap);
			}
		} catch (Exception e) {
			throw new InstantiationException("loading submitter failed because"
					+ e.getMessage());
		}
		throw new IllegalArgumentException("invalid submit type '" + submitType
				+ "'");
	}

	private Submitter loadSubmitter(String appName, Map<String, String> argsMap)
			throws InstantiationException {
		AppProfile profile = getAppProfile(appName);
		return this.loadSubmitter(appName, profile.getSubmitterImpLang(),
				profile.getSubmitType(), argsMap);
	}

	public ServiceResponse upload(String appName, Map<String, String> argsMap,
			String toPath, DataHandler data) throws IOException,
			InstantiationException {

		Submitter submitter = loadSubmitter(appName, argsMap);
		SubmitterResponse result = submitter.write(data, toPath, false);
		return new ServiceResponse(result);
	}

	public DataHandler download(String appName, Map<String, String> argsMap,
			String fromPath) throws IOException, InstantiationException {

		Submitter submitter = loadSubmitter(appName, argsMap);
		ByteArrayDataSource ds = submitter.readAsByteArray(fromPath, true);
		logger.info("HEREQ");
		return new DataHandler(ds);
	}

	public static String getCamelWord(String underscore) {
		StringBuffer buffer = new StringBuffer();
		for (String words : underscore.split("_")) {
			buffer.append(words.substring(0, 1).toUpperCase());
			buffer.append(words.substring(1));
		}
		return buffer.toString();
	}

	public String getFileContent(String appName, Map<String, String> argsMap)
			throws IOException, InstantiationException {
		if (argsMap == null)
			throw new IllegalArgumentException("argsMap is empty");
		logger.debug("------ Argument Map ------");
		for (String key : argsMap.keySet()) {
			logger.debug(String.format("\t%s => %s", key, argsMap.get(key)));
		}
		logger.debug("--------------------------");

		Submitter submitter = loadSubmitter(appName, argsMap);
		return submitter.readAsString();
	}
}
